/*
 * TowerBase.cs
 *
 * Created on November 24, 2007, 3:19 PM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 * Copyright (C) 2010       Mathijs Miermans
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Diagnostics;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


namespace HexTD
{
	public abstract class TowerBase : DrawableGameComponent, Tower
	{

		protected float paintScale;
		protected Context context;

		private TowerFactory.type type;
		private TowerFireControl fc;
		protected List<TowerUpgradeDamage> upgradeDamage;
		protected List<TowerUpgradeRange> upgradeRange;
		protected int mode = 0;
		protected string[] modes = new string[] { "Close", "Hard", "Weak", "Furthest" };
		protected bool retarget = false;

		protected Hex hex;
		protected int[] hexLocation;
		protected float xFloat, yFloat;
		protected float xPaint, yPaint;

		protected int level = 0;
		protected float rangeBase = 0;
		protected float rangeCurrent = 0;
		protected float rangeCurrent2 = 0;
		protected float rangePaint = 0;
		protected int damageBase = 0;
		protected int damageCurrent = 0;
		protected string damageText = "";
		protected string rangeText = "";

		protected static string form = "#,#.##";
		protected PanelTowerSettings pts;

		protected Color lineColor;
		protected Color rangeColor;
#if PORT
		protected Shape[] shapes = new Shape[0];
#endif
		protected int[] shapeAction;
		protected Color[] shapeColor;
#if PORT
		protected Brush[] strokes;
		protected EllipseGeometry rangeCircle;
		protected Brush rangeStroke;
		protected FontFamily levelFont;
#endif
		protected float[] levelLocation;
#if PORT
		protected RotateTransform atTranslate = new RotateTransform();
		private RotateTransform atCorrect;
#endif

		private bool bodyUsesSVG;
		private string bodySVGName;
		private float bodyScale;

		protected bool selected = false;
		protected bool selling = false;
		private int price = 0;

		public event EventHandler changed;

		/**
		 * Creates a new instance of TowerBase
		 */
		public TowerBase(Game game, TowerFactory.type t, int price, int damage, float range) : base(game)
		{
			this.price = price;
			this.type = t;
			this.damageBase = damage;
			this.rangeBase = range;
			this.levelLocation = new float[2];
		}

		public abstract void doTick(int gameTime);
		public abstract string getInfoString();
		public abstract string getStatusString();
		public abstract void DrawEffects(GameTime gameTime);

		protected virtual void doInit(Context context, Hex hex)
		{
#if PORT
			this.context = context;
			this.rangeColor = this.context.colors.getColorRangeCircle();
			this.damageCurrent = this.damageBase;
        
			this.hex = hex;
			if (hex != null) {
				this.hexLocation = hex.getLocation();
			}
        
			this.upgradeDamage = new List<TowerUpgradeDamage>();
			this.upgradeRange = new List<TowerUpgradeRange>();
			PanelTowerSettings p = new PanelTowerSettings(this, this.context);
			p.setModes(this.modes);
			this.pts = p;
			this.atCorrect = new RotateTransform();
			this.resetScale();
#endif
		}

		public virtual bool setMode(int m, int v)
		{
#if PORT
			if (m==1) {
				this.mode = v;
				if (this.mode >= this.modes.length) {
					this.mode = 0;
				}
				this.retarget = true;
				if (this.pts.getMode(m) != this.mode) {
					this.pts.setMode(m, this.mode);
				}
				return true;
			} else {
				System.Diagnostics.Debug.WriteLine("TowerBase::setMode: unknown mode: "+m);
				return false;
			}
#else
			return false;
#endif
		}

		public int nextMode(int m)
		{
			return this.pts.nextMode(m);
		}

		public virtual int getMode(int m)
		{
			if (m == 1)
			{
				return this.mode;
			}
			else
			{
				System.Diagnostics.Debug.WriteLine("TowerBase::setMode: unknown mode: " + m);
				return -1;
			}
		}

		public float getRangeBase()
		{
			return this.rangeBase;
		}

		public float getRangeCurrent()
		{
			return this.rangeCurrent;
		}

		public int getUpgradePrice()
		{
			return (int)Math.Round(0.5f * this.price);
		}

		public int getSellPrice()
		{
			return (int)Math.Round(0.75f * (1.0f + 0.5f * this.level) * this.price);
		}

		public virtual bool canUpgrade()
		{
			return (this.level < 9);
		}

		public void doUpgrade()
		{
			this.level++;
			this.damageCurrent = (int)(this.damageBase * (float)Math.Pow(1.45, this.level));
			this.calcDamageRange();
			this.fireTowerChangedEvent();
		}
		protected virtual void calcDamageRange()
		{
#if PORT
			float lvl = (float)Math.Pow(1.45, this.level);
			float upg = (1f + 0.25f * this.upgradeDamage.Count);
			this.damageCurrent = (int)(this.damageBase * lvl * upg);
			this.damageText = "" + this.damageBase.ToString(form) + "*" + lvl.ToString(form) + "*" + upg.ToString(form) + "=" + this.damageCurrent.ToString(form);

			lvl = (float)Math.Pow(1.07, this.level);
			upg = (1f + 0.25f * this.upgradeRange.Count);
			this.rangeCurrent = this.rangeBase * lvl * upg;
			this.rangeText = "" + this.rangeBase.ToString(form) + "*" + lvl.ToString(form) + "*" + upg.ToString(form) + "=" + this.rangeCurrent.ToString(form);

			this.rangePaint = this.rangeCurrent * this.paintScale;
			this.rangeCurrent2 = this.rangeCurrent * this.rangeCurrent;
			this.rangeCircle = new EllipseGeometry();
			this.rangeCircle.Center = new Point(-rangePaint, -rangePaint);
			this.rangeCircle.RadiusX = rangePaint * 2;
			this.rangeCircle.RadiusY = rangePaint * 2;
#endif
		}

		protected abstract void generateShapes();

		public virtual string getSvgName()
		{
			return null;
		}

		public void setSelected(bool selected)
		{
			this.selected = selected;
		}

		/* Non specific enemy finders
		 * There are used for bouncing projectiles and splash damage
		 * These can not be deferred to a firecontrol tower.
		 */

		protected EnemySprite[] findEnemiesInRange(float x, float y, float r)
		{
			List<EnemySprite> tempEnemies = new List<EnemySprite>();
			float r2 = r * r;
			float dx, dy, d2;
			if (this.context.enemies != null)
			{
				for (int i = 0; i < this.context.enemies.Length; i++)
				{
					if (this.context.enemies[i].validTarget())
					{
						dx = this.context.enemies[i].getX() - x;
						dy = this.context.enemies[i].getY() - y;
						d2 = dx * dx + dy * dy;
						if (d2 < r2)
						{
							tempEnemies.Add(this.context.enemies[i]);
						}
					}
				}
			}
			return tempEnemies.ToArray();
		}

		protected int findEnemyClosest(float x, float y, float r, int[] not)
		{

			int foundEnemy = -1;
			float distance2Min = r * r;
			float distance2;
			float dx, dy;
			bool found;
			if (this.context.enemies != null)
			{
				for (int i = 0; i < this.context.enemies.Length; i++)
				{
					found = false;
					for (int n = 0; n < not.Length; n++)
					{
						if (not[n] == i)
						{
							found = true;
							continue;
						}
					}
					if (!found && this.context.enemies[i].validTarget())
					{
						dx = this.context.enemies[i].getX() - x;
						dy = this.context.enemies[i].getY() - y;
						distance2 = dx * dx + dy * dy;
						if (distance2 < distance2Min)
						{
							distance2Min = distance2;
							foundEnemy = i;
						}
					}
				}
			}
			return foundEnemy;
		}

		/* Specific enemy finders
		 * These can not be deferred to a firecontrol tower.
		 */

		protected EnemySprite[] findEnemiesFastest(float x, float y, float r, int n, bool exShielded)
		{
			return this.findEnemiesFastest(x, y, r, n, new EnemySprite[0], exShielded);
		}

		protected EnemySprite[] findEnemiesFastest(float x, float y, float r, EnemySprite[] preferred, int n, bool exShielded)
		{
			List<EnemySprite> found = new List<EnemySprite>(n);
			float r2 = r * r;
			float dx, dy, d2;
			EnemySprite pe;
			for (int p = 0; p < preferred.Length; p++)
			{
				pe = preferred[p];
				if (pe.validTarget())
				{
					dx = pe.getX() - x;
					dy = pe.getY() - y;
					d2 = dx * dx + dy * dy;
					if (d2 < r2)
					{
						found.Add(pe);
					}
				}
			}
			EnemySprite[] not = found.ToArray();
			EnemySprite[] extra = this.findEnemiesFastest(x, y, r, n - not.Length, not, exShielded);
			for (int e = 0; e < extra.Length; e++)
			{
				found.Add(extra[e]);
			}
			return found.ToArray();
		}

		protected EnemySprite[] findEnemiesFastest(float x, float y, float r, int n, EnemySprite[] not, bool exShielded)
		{

			List<EnemySprite> tempEnemies = new List<EnemySprite>();
			float r2 = r * r;
			float dx, dy, d2;
			int slowest = -1;
			float sSpeed = 9999;
			float speed = 0;
			bool found;
			EnemySprite e;
			if (this.context.enemies != null)
			{
				for (int i = 0; i < this.context.enemies.Length; i++)
				{
					e = this.context.enemies[i];
					if (e.validTarget() && !(exShielded && e.isShielded()))
					{
						dx = e.getX() - x;
						dy = e.getY() - y;
						d2 = dx * dx + dy * dy;
						if (d2 < r2)
						{
							found = false;
							for (int j = 0; j < not.Length; j++)
							{
								if (not[j] == e)
								{
									found = true;
									break;
								}
							}
							if (!found)
							{
								speed = e.getSpeed();
								if (tempEnemies.Count < n)
								{
									tempEnemies.Add(e);
									if (speed < sSpeed)
									{
										sSpeed = speed;
										slowest = tempEnemies.Count - 1;
									}
								}
								else
								{
									if (speed > sSpeed)
									{
										tempEnemies.RemoveAt(slowest);
										tempEnemies.Add(e);
										sSpeed = 9999;
										for (int s = tempEnemies.Count - 1; s >= 0; s--)
										{
											float tempSpeed = tempEnemies[s].getSpeed();
											if (tempSpeed < sSpeed)
											{
												sSpeed = tempSpeed;
												slowest = s;
											}
										}
									}
								}
							}
						}
					}
				}
			}
			return tempEnemies.ToArray();
		}

		/* Specific enemy finders
		 * These can be deferred to a firecontrol tower.
		 */
		public int findEnemy(int preferedEnemyNr, bool exShielded)
		{
			if (this.retarget)
			{
				preferedEnemyNr = -1;
				this.retarget = false;
			}
			if (this.fc != null)
			{
				return this.fc.findEnemy(this, preferedEnemyNr, exShielded);
			}

			switch (this.mode)
			{
				case 0:
					return this.findEnemyClosest(preferedEnemyNr, exShielded);
				case 1:
					return this.findEnemyHardest(preferedEnemyNr, exShielded);
				case 2:
					return this.findEnemyWeakest(preferedEnemyNr, exShielded);
				case 3:
					return this.findEnemyFurthest(preferedEnemyNr, exShielded);
				default:
					System.Diagnostics.Debug.WriteLine("TowerBase::FindEnemy: Unknown mode " + this.mode);
					break;
			}
			return -1;
		}

		private int findEnemyClosest(int preferedEnemyNr, bool exShielded)
		{
			int foundEnemy = -1;
			float distance2Min = 1E10f;
			float distance2;
			float dx, dy;
			EnemySprite e;
			if (this.context.enemies != null)
			{
				if (preferedEnemyNr >= 0 && preferedEnemyNr < this.context.enemies.Length)
				{
					e = this.context.enemies[preferedEnemyNr];
					if (e.validTarget() && !(exShielded && e.isShielded()))
					{
						dx = this.context.enemies[preferedEnemyNr].getX() - this.xFloat;
						dy = this.context.enemies[preferedEnemyNr].getY() - this.yFloat;
						distance2 = dx * dx + dy * dy;
						if (distance2 < this.rangeCurrent2)
						{
							return preferedEnemyNr;
						}
					}
				}
				for (int i = 0; i < this.context.enemies.Length; i++)
				{
					e = this.context.enemies[i];
					if (e.validTarget() && !(exShielded && e.isShielded()))
					{
						dx = this.context.enemies[i].getX() - this.xFloat;
						dy = this.context.enemies[i].getY() - this.yFloat;
						distance2 = dx * dx + dy * dy;
						if (distance2 < this.rangeCurrent2 && distance2 < distance2Min)
						{
							distance2Min = distance2;
							foundEnemy = i;
						}
					}
				}
			}
			return foundEnemy;
		}
		private int findEnemyWeakest(int preferedEnemyNr, bool exShielded)
		{

			int foundEnemy = -1;
			float distance2;
			long healthMin = long.MaxValue;
			long health;
			float dx, dy;
			EnemySprite e;
			if (this.context.enemies != null)
			{
				if (preferedEnemyNr >= 0 && preferedEnemyNr < this.context.enemies.Length)
				{
					e = this.context.enemies[preferedEnemyNr];
					if (e.validTarget() && !(exShielded && e.isShielded()))
					{
						dx = this.context.enemies[preferedEnemyNr].getX() - this.xFloat;
						dy = this.context.enemies[preferedEnemyNr].getY() - this.yFloat;
						distance2 = dx * dx + dy * dy;
						if (distance2 < this.rangeCurrent2)
						{
							return preferedEnemyNr;
						}
					}
				}
				for (int i = 0; i < this.context.enemies.Length; i++)
				{
					e = this.context.enemies[i];
					if (e.validTarget() && !(exShielded && e.isShielded()))
					{
						dx = this.context.enemies[i].getX() - this.xFloat;
						dy = this.context.enemies[i].getY() - this.yFloat;
						distance2 = dx * dx + dy * dy;
						health = this.context.enemies[i].getHealth();
						if (distance2 < this.rangeCurrent2 && health < healthMin)
						{
							healthMin = health;
							foundEnemy = i;
						}
					}
				}
			}
			return foundEnemy;
		}
		private int findEnemyHardest(int preferedEnemyNr, bool exShielded)
		{

			int foundEnemy = -1;
			float distance2;
			long healthMax = 0;
			long health;
			float dx, dy;
			EnemySprite e;
			if (this.context.enemies != null)
			{
				if (preferedEnemyNr >= 0 && preferedEnemyNr < this.context.enemies.Length)
				{
					e = this.context.enemies[preferedEnemyNr];
					if (e.validTarget() && !(exShielded && e.isShielded()))
					{
						dx = this.context.enemies[preferedEnemyNr].getX() - this.xFloat;
						dy = this.context.enemies[preferedEnemyNr].getY() - this.yFloat;
						distance2 = dx * dx + dy * dy;
						if (distance2 < this.rangeCurrent2)
						{
							return preferedEnemyNr;
						}
					}
				}
				for (int i = 0; i < this.context.enemies.Length; i++)
				{
					e = this.context.enemies[i];
					if (e.validTarget() && !(exShielded && e.isShielded()))
					{
						dx = this.context.enemies[i].getX() - this.xFloat;
						dy = this.context.enemies[i].getY() - this.yFloat;
						distance2 = dx * dx + dy * dy;
						health = this.context.enemies[i].getHealth();
						if (distance2 < this.rangeCurrent2 && health > healthMax)
						{
							healthMax = health;
							foundEnemy = i;
						}
					}
				}
			}
			return foundEnemy;
		}
		private int findEnemyFurthest(int preferedEnemyNr, bool exShielded)
		{

			int foundEnemy = -1;
			float distance2;
			float progressMax = -1;
			float progress;
			float dx, dy;
			EnemySprite e;
			if (this.context.enemies != null)
			{
				if (preferedEnemyNr >= 0 && preferedEnemyNr < this.context.enemies.Length)
				{
					e = this.context.enemies[preferedEnemyNr];
					if (e.validTarget() && !(exShielded && e.isShielded()))
					{
						dx = this.context.enemies[preferedEnemyNr].getX() - this.xFloat;
						dy = this.context.enemies[preferedEnemyNr].getY() - this.yFloat;
						distance2 = dx * dx + dy * dy;
						if (distance2 < this.rangeCurrent2)
						{
							return preferedEnemyNr;
						}
					}
				}
				for (int i = 0; i < this.context.enemies.Length; i++)
				{
					e = this.context.enemies[i];
					if (e.validTarget() && !(exShielded && e.isShielded()))
					{
						dx = this.context.enemies[i].getX() - this.xFloat;
						dy = this.context.enemies[i].getY() - this.yFloat;
						distance2 = dx * dx + dy * dy;
						progress = this.context.enemies[i].getProgress();
						if (distance2 < this.rangeCurrent2 && progress > progressMax)
						{
							progressMax = progress;
							foundEnemy = i;
						}
					}
				}
			}
			return foundEnemy;
		}

		public void resetScale()
		{
#if PORT
			this.paintScale = this.context.paintScale;

			this.generateShapes();
			if (this.hex != null)
			{
				float[] realLocation = this.hex.getFloatLocation();
				this.xFloat = realLocation[0];
				this.yFloat = realLocation[1];
			}
			else
			{
				this.xFloat = 1f;
				this.yFloat = 1f;
			}
			this.xPaint = this.xFloat * this.paintScale + this.context.offsetX;
			this.yPaint = this.yFloat * this.paintScale + this.context.offsetY;
			atTranslate.setToIdentity();
			atTranslate.translate(this.xPaint, this.yPaint);

			this.calcDamageRange();
			this.rangeStroke = new Brush(1.0f * this.context.strokeScale, Brush.CAP_ROUND, Brush.JOIN_BEVEL);

			this.levelFont = new Font(Font.DIALOG, Font.PLAIN, (int)(0.40 * this.paintScale));
			this.levelLocation[0] = -0.95f * this.context.hexPaintWidth;
			this.levelLocation[1] = 0.5f * this.paintScale;

			this.bodySVGName = this.getSvgName();
			if (this.bodySVGName != null)
			{
				this.bodyUsesSVG = true;
				this.bodyScale = this.context.paintScale * 1.5f;
				atCorrect.setToIdentity();
				atCorrect.setToTranslation(-this.bodyScale / 2, -this.bodyScale / 2);
			}
#endif
		}

		public override void Draw(GameTime gameTime)
		{
#if PORT
			Brush defaultStroke = g2.getStroke();
			RotateTransform saveXform = g2.getTransform();

			g2.transform(atTranslate);
			g2.setColor(this.lineColor);
			g2.setFont(this.levelFont);
			g2.drawString("" + (this.level + 1), this.levelLocation[0], this.levelLocation[1]);

			if (this.selected)
			{
				g2.setColor(this.rangeColor);
				g2.draw(this.rangeCircle);
			}

			if (this.bodyUsesSVG)
			{
				g2.transform(this.atCorrect);
				context.renderSvgImage(this.bodySVGName, g2, (int)this.bodyScale, (int)this.bodyScale);
			}
			else
			{
				for (int i = 0; i < this.shapes.Length; i++)
				{
					g2.setColor(this.shapeColor[i]);
					switch (this.shapeAction[i])
					{
						case 0:
							g2.setStroke(this.strokes[i]);
							g2.fill(this.shapes[i]);
							g2.draw(this.shapes[i]);
							break;
						case 1:
							g2.setStroke(this.strokes[i]);
							g2.draw(this.shapes[i]);
							break;
						case 2:
							g2.fill(this.shapes[i]);
							break;
					}
				}
			}

			g2.setTransform(saveXform);
			g2.setStroke(defaultStroke);
#endif
		}

		public Hex getHex()
		{
			return hex;
		}

		public void setHex(Hex hex)
		{
			this.hex = hex;
		}

		public void registerTower(Tower t)
		{
#if PORT
			if (t != this && !this.selling)
			{
				switch (t.getType())
				{
					case firecontrol:
						if (this.type != TowerFactory.type.upgradeDamage && this.type != TowerFactory.type.upgradeRange && this.type != TowerFactory.type.firecontrol)
						{
							if (this.fc != null)
							{
								this.fc.removeClient(this);
							}
							this.fc = (TowerFireControl)t;
							this.fc.addClient(this);
						}
						break;
					case upgradeDamage:
						if (this.type != TowerFactory.type.upgradeDamage && this.type != TowerFactory.type.upgradeRange && this.type != TowerFactory.type.firecontrol)
						{
							if (!this.upgradeDamage.contains(t))
							{
								TowerUpgradeDamage tud = (TowerUpgradeDamage)t;
								this.upgradeDamage.Add(tud);
								tud.addClient(this);
								this.calcDamageRange();
							}
						}
						break;
					case upgradeRange:
						if (!this.upgradeRange.contains(t))
						{
							TowerUpgradeRange tur = (TowerUpgradeRange)t;
							this.upgradeRange.Add(tur);
							tur.addClient(this);
							this.calcDamageRange();
						}
						break;
				}
			}
#endif
		}

		public void unregisterTower(Tower t)
		{
#if PORT
			switch (t.getType())
			{
				case firecontrol:
					if (this.fc != null)
					{
						this.fc.removeClient(this);
						this.fc = null;
					}
					break;
				case upgradeDamage:
					TowerUpgradeDamage tud = (TowerUpgradeDamage)t;
					this.upgradeDamage.Remove(t);
					tud.removeClient(this);
					break;
				case upgradeRange:
					TowerUpgradeRange tur = (TowerUpgradeRange)t;
					this.upgradeRange.Remove(t);
					tur.removeClient(this);
					break;
			}
			this.calcDamageRange();
#endif
		}

		public virtual void doCleanup()
		{
#if PORT
			this.selling = true;
			if (this.fc != null)
			{
				this.fc.removeClient(this);
				this.fc = null;
			}
			int i;
			for (i = this.upgradeDamage.Count - 1; i >= 0; i--)
			{
				TowerUpgradeDamage tud = this.upgradeDamage.Remove(i);
				tud.removeClient(this);
			}
			for (i = this.upgradeRange.Count - 1; i >= 0; i--)
			{
				TowerUpgradeRange tur = this.upgradeRange.Remove(i);
				tur.removeClient(this);
			}
#endif
		}

		public TowerFactory.type getType()
		{
			return type;
		}

		public PanelTowerSettings getButtons()
		{
			return this.pts;
		}

		protected void fireTowerChangedEvent()
		{
			if (changed != null)
			{
				changed(this, null);
			}
		}

	}
}